home *** CD-ROM | disk | FTP | other *** search
/ The Best of MacTutor - S…e Code for Volumes 1 to 5 / The Best of MacTutor - Source Code for Volume 1-5 (Wayzata Technology)(6031)(1990).bin / Source Code / #43 (Apr 89) / ScrollMenu Source / scrollMenuBarPatch.c < prev    next >
C/C++ Source or Header  |  1987-11-22  |  12KB  |  453 lines

  1. /* scrollMenuBarPatch.c        18 Nov 1987
  2.   *
  3.   * by Mike Scanlin and Andy Voelker
  4.   *
  5.   * This will install a patch to _GetNextEvent that intercepts mouseDown events if they occur
  6.   * in the very top pixel-row of the screen. If an event is intercepted, the menu bar is scrolled
  7.   * off the right side of the screen before the user can react. After 4 more clicks in the menuBar
  8.   * (with appropriately sarcastic messages displayed after each one) the original menuBar is
  9.   * restored. The patch can be removed by typing COMMAND-OPTION-SHIFT-TAB.
  10.   */
  11.  
  12. #include "EventMgr.h"
  13. #include "QuickDraw.h"
  14. #include "asm.h"
  15.  
  16. /* low memory globals */
  17. extern Handle    MenuList        : 0x0A1C;
  18. extern Ptr    ScrnBase        : 0x0824;
  19. extern GrafPtr    WMgrPort        : 0x09DE;
  20.  
  21. /* the traps we patch */
  22. #define    GetNextEventTrap    0xA970
  23. #define    DrawMenuBarTrap    0xA937
  24. #define    HiliteMenuTrap        0xA938
  25.  
  26. #define    CLICKS_TO_MENU        4
  27. #define    MESSAGE_DELAY        60
  28. #define    MENU_BAR_HEIGHT        20
  29. #define    TAB_KEY                0x09
  30. #define    JMP_INSTRUCTION        0x4EF9
  31.  
  32. #define    memFullErr            -108
  33. #define    screenBits_bounds_right    -110
  34. #define    screenBits_bounds_left    -114
  35.  
  36. void     main(void);
  37.  
  38. void main()
  39. {
  40.     asm {
  41.     
  42. /* This first section is the only part that gets run initially. It gets some space in the system heap
  43.   * and sets up a patch to _GetNextEvent. The patch won't do anything until the user clicks in the
  44.   * very top pixel of the menu bar (in which case it will scroll the menu bar off the screen to
  45.   * the right). */
  46.  
  47.           move.l    D3,-(SP)
  48.  
  49. /* get the old trap address */    
  50.         move    #GetNextEventTrap,D0
  51.         _GetTrapAddress
  52.  
  53. /* set the address for the JMP instruction that calls the original trap */
  54.         lea        @origTrap,A1
  55.         move.l    A0,(A1)
  56.  
  57. /* get some space in the system heap for our patch */
  58.         lea        @last,A0
  59.         lea        @first,A1
  60.         suba.l    A1,A0
  61.         move.l    A0,D0        /* D0 = length of patch */
  62.         move.l    D0,D3        /* save for _BlockMove */
  63.         _NewPtr    SYS
  64.         cmpi        #memFullErr,D0
  65.         beq.s        @noPatch
  66.         lea        @saveLoc,A1
  67.         move.l    A0,(A1)        /* save for removePatch */
  68.         move.l    A0,-(SP)        /* save for _BlockMove */
  69.  
  70. /* set the trap address to the space we just got in the system heap. */
  71.         move    #GetNextEventTrap,D0
  72.         _SetTrapAddress
  73.  
  74. /* now move our patch into place */    
  75.         lea        @first,A0
  76.         move.l    (SP)+,A1        /* result from _NewPtr */
  77.         move.l    D3,D0
  78.         _BlockMove
  79.  
  80. @noPatch
  81.         move.l    (SP)+,D3
  82.  
  83. /* this is the end of the installation part, but we can't do an
  84.   * RTS here because LSC needs to clean up. So we fall through
  85.   * to the end. */
  86.         bra        @last
  87.  
  88. /**********************************************
  89.   * Here's the new _GetNextEvent. It calls the existing _GetNextEvent
  90.   * and then checks if a mouseDown or keyDown event is being reported.
  91.   * If not, the event is passed to the application unmodified. If we end up
  92.   * using the event ourselves, a null event is returned to the application.
  93.   **********************************************/
  94.  
  95. @first
  96. /* pop the original return address and save it */
  97.         lea        @exitAddress,A0
  98.         move.l    (SP)+,(A0)
  99. /* save ptr to event record so we can get at it later */
  100.         lea        @eventRecPtr,A0
  101.         move.l    (SP),(A0)
  102. /* set the return address to our patch */
  103.         pea        @tailPatch
  104.  
  105. /* the nops get filled with the address of the original _GetNextEvent */
  106.         dc        JMP_INSTRUCTION
  107. @origTrap    
  108.         nop
  109.         nop
  110.  
  111. /* this is where it comes after the normal _GetNextEvent processing */
  112. @tailPatch
  113.         movem.l    A1/D0-D3,-(SP)
  114.         
  115.         lea        @eventRecPtr,A0
  116.         move.l    (A0),A0
  117.  
  118. /* check if it's a keydown event that says to remove ourself. This is the only keyDown 
  119.   * that we intercept. */
  120.         move    OFFSET(EventRecord,what)(A0),D0
  121.         cmpi        #keyDown,D0
  122.         bne.s        @noKeyDown
  123. /* the key to remove the patch is COMMAND-SHIFT-OPTION-Tab */
  124.         move.l    OFFSET(EventRecord,message)(A0),D0
  125.         cmpi.b    #TAB_KEY,D0
  126.         bne.s        @noKeyDown
  127.         move    OFFSET(EventRecord,modifiers)(A0),D0
  128.         andi        #optionKey + cmdKey + shiftKey,D0
  129.         eori        #optionKey + cmdKey + shiftKey,D0
  130.         beq.s        @removePatch
  131.         
  132. @noKeyDown
  133. /* if it's not a mousedown event, then ignore it */
  134.         cmpi        #mouseDown,D0
  135.         bne.s        @patchExit
  136.  
  137. /* if we've already scrolled the menu list, then don't scroll it again */
  138.         lea        @menus,A1
  139.         tst        (A1)
  140.         bne.s        @alreadyGone
  141.         
  142. /* if no menus exist, then leave */
  143.         move.l    MenuList,A1
  144.         move.l    (A1),A1
  145.         tst        (A1)
  146.         beq.s        @patchExit
  147.  
  148. /* if the mouse is not at an odd location, then leave */
  149.         move.l    OFFSET(EventRecord,where)(A0),D0
  150.         andi        #1,D0
  151.         beq.s        @patchExit
  152. /* if the mouse is not at the very top pixel of the menu bar, then leave */
  153.         move.l    OFFSET(EventRecord,where)(A0),D0
  154. /* put vertical coordinate in low word */
  155.         swap        D0
  156.         cmpi        #1,D0
  157.         bge.s        @patchExit
  158.         
  159. /* now we're set to scroll that puppy. Do it... */
  160.         bsr        @scrollMenuBar
  161.  
  162. /* save the fact that the menu bar that was just scrolled */
  163.         lea        @menus,A0
  164.         move    #1,(A0)
  165.         
  166. /* patch _DrawMenuBar and _HiliteMenu to do nothing if and when they're called */
  167.         bsr        @disableTraps
  168.  
  169.         lea        @clicks,A1
  170.         move    #CLICKS_TO_MENU,(A1)
  171.         
  172.         bra.s        @returnNullEvent
  173.  
  174. @alreadyGone
  175. /* if mouse is not in menu bar, then leave */
  176.         move.l    OFFSET(EventRecord,where)(A0),D0
  177. /* put vertical coordinate in low word */
  178.         swap        D0
  179.         cmpi        #MENU_BAR_HEIGHT,D0
  180.         bge.s        @patchExit
  181.  
  182. /* print a message in the menu bar */        
  183.         bsr        @drawAMessage
  184.  
  185.         lea        @clicks,A0
  186.         subi        #1,(A0)
  187.         bne.s        @returnNullEvent
  188.  
  189. /* now that we're finished playing, restore _DrawMenuBar and _HiliteMenu */
  190.         bsr        @restoreMenus
  191.         
  192. @returnNullEvent
  193. /* set the event to null */
  194.         lea        @eventRecPtr,A0
  195.         move.l    (A0),A0
  196.         clr        OFFSET(EventRecord,what)(A0)
  197. /* change _GetNextEvent's return value to false.
  198.   * the 20 is for the 5 regs that are saved on the stack at this point */
  199.         clr        20(SP)
  200.  
  201. @patchExit
  202.         movem.l    (SP)+,A1/D0-D3
  203. /* JMP to the place that called _GetNextEvent */
  204.         dc        JMP_INSTRUCTION
  205. @exitAddress
  206.         nop
  207.         nop
  208.  
  209.  
  210. /* this is where it comes to remove the _GetNextEvent patch */
  211. @removePatch
  212. /* if the menus aren't shown, then restore them before leaving */
  213.         bsr        @restoreMenus        
  214. /* check if we are the most recent patch to _GetNextEvent.
  215.   * If we're not, then don't unpatch. */
  216.           move    #GetNextEventTrap,D0
  217.         _GetTrapAddress
  218.         lea        @first,A1
  219.         cmpa.l    A1,A0
  220.         bne.s        @returnNullEvent
  221. /* set the trap address back to the original trap. */
  222.         lea        @origTrap,A0
  223.         move.l    (A0),A0
  224.         move    #GetNextEventTrap,D0
  225.         _SetTrapAddress
  226. /* beep to let them know it has been removed */
  227.         move    #1,-(SP)
  228.         _SysBeep
  229. /* free up the mem occupied by this patch */
  230.         lea        @saveLoc,A0
  231.         move.l    (A0),A0
  232.         _DisposPtr
  233.         bra.s        @returnNullEvent
  234.  
  235.  
  236. /* This routine will disable _DrawMenuBar and _HiliteMenu */
  237. @disableTraps
  238.         move    #DrawMenuBarTrap,D0
  239.         _GetTrapAddress
  240.         lea        @drawMenuBarAddr,A1
  241.         move.l    A0,(A1)
  242.         lea        @doNothing,A0
  243.         move    #DrawMenuBarTrap,D0
  244.         _SetTrapAddress
  245.         move    #HiliteMenuTrap,D0
  246.         _GetTrapAddress
  247.         lea        @hiliteMenuAddr,A1
  248.         move.l    A0,(A1)
  249.         lea        @doNothingWithParam,A0
  250.         move    #HiliteMenuTrap,D0
  251.         _SetTrapAddress
  252.         rts
  253.         
  254.  
  255. /* This routine will restore _DrawMenuBar and _HiliteMenu and
  256.   * then draw the current menu bar */
  257. @restoreMenus
  258.         lea        @menus,A0
  259.         tst        (A0)
  260.         beq.s        @doNothing
  261. /* unpatch DrawMenuBar and then draw the old menu bar */
  262.         lea        @drawMenuBarAddr,A0
  263.         move.l    (A0),A0
  264.         move    #DrawMenuBarTrap,D0
  265.         _SetTrapAddress
  266.         lea        @hiliteMenuAddr,A0
  267.         move.l    (A0),A0
  268.         move    #HiliteMenuTrap,D0
  269.         _SetTrapAddress
  270.         lea        @menus,A0
  271.         clr        (A0)
  272.         _DrawMenuBar
  273. @doNothing
  274.         rts
  275.  
  276.  
  277. /* This is the _HiliteMenu routine that does nothing. It gets rid of the parameter passed
  278.   * to _HiliteMenu and then returns. */
  279. @doNothingWithParam
  280.         move.l    (SP)+,A0
  281.         addq.l    #2,SP
  282.         jmp        (A0)
  283.  
  284.  
  285. /* This is the routine that actually does the scrolling */
  286. @scrollMenuBar
  287. /* hide the cursor so we don't get part of the cursor scrolled with the menuBar */
  288.         _HideCursor
  289.  
  290. /* get the current screen width from the Quickdraw global screenBits */
  291.         move.l    (A5),A0
  292. /* D3 = # of columns (width) - 1 in the current screen */
  293.         move    screenBits_bounds_right(A0),D3
  294.         sub        screenBits_bounds_left(A0),D3
  295.         move    D3,D2
  296.         subq        #1,D3
  297. /* D2 = # of bytes - 1 in one row of screen */    
  298.         asr        #3,D2
  299.         subq        #1,D2
  300.  
  301. /* here's the loop that actually scrolls the menu bar all the way across the screen */
  302. @wayOut
  303.         move    #MENU_BAR_HEIGHT - 2,D1
  304.  
  305. /* this outside loop will scroll all rows of the menu bar one pixel to the right */
  306. @outside
  307.         move    D2,D0
  308.         addq        #1,D0
  309.         mulu        D1,D0        /* calc # of bytes from base addr to start of current row */
  310.         move.l    ScrnBase,A0
  311.         adda.l    D0,A0
  312.  
  313.         clr.b        (A0)        /* white-out the left edge of this row */
  314.  
  315.         move    D2,D0        /* D0 = number of words in one row */
  316.         asr        #1,D0
  317.         
  318.         andi.b    #0xEF,CCR    /* set the X flag */
  319. /* this inner loop will scroll one row of the screen one pixel to the right */
  320. @inside
  321.         roxr        (A0)+
  322.         dbra        D0,@inside
  323.  
  324. /* go and do the next row */
  325.         dbra        D1,@outside
  326.  
  327. /* make the corners look like they used to (i.e. rounded and black) */
  328.         move    D2,D0
  329.         move.l    ScrnBase,A1
  330.         ori.b        #0xF8,(A1)
  331.         adda        D0,A1
  332.         ori.b        #0x1F,(A1)+
  333.         ori.b        #0xE0,(A1)
  334.         adda        D0,A1
  335.         ori.b        #0x07,(A1)+
  336.         ori.b        #0xC0,(A1)
  337.         adda        D0,A1
  338.         ori.b        #0x03,(A1)+
  339.         ori.b        #0x80,(A1)
  340.         adda        D0,A1
  341.         ori.b        #0x01,(A1)+
  342.         ori.b        #0x80,(A1)
  343.         adda        D0,A1
  344.         ori.b        #0x01,(A1)
  345.         
  346.         dbra        D3,@wayOut
  347.  
  348.         _ShowCursor
  349.         rts
  350.  
  351.  
  352. /* this routine will print a string in the menu bar, wait a bit and then erase it */
  353. @drawAMessage
  354. /* set the current port to the window manager's */
  355.         move.l    (A5),A0
  356.         lea        @savePort,A1
  357.         move.l    (A0),(A1)
  358.         move.l    WMgrPort,(A0)
  359.  
  360. /* save the old clipRgn */
  361.         lea        @saveClip,A0
  362.         move.l    WMgrPort,A1
  363.         move.l    OFFSET(GrafPort,clipRgn)(A1),(A0)
  364. /* set the clipRgn to be big enough for the string we want to print */
  365.         subq        #4,SP
  366.         _NewRgn
  367.         move.l    WMgrPort,A1
  368.         move.l    (SP)+,OFFSET(GrafPort,clipRgn)(A1)
  369.         pea        @stringRect
  370.         _ClipRect
  371.  
  372. /* the string we print depends on the value of the clicks variable */
  373.         lea        @clicks,A0
  374.         move    (A0),D0
  375.         cmpi        #4,D0
  376.         bne.s        @1
  377.         pea        @string1
  378.         bra.s        @5
  379. @1        cmpi        #3,D0
  380.         bne.s        @2
  381.         pea        @string2
  382.         bra.s        @5
  383. @2        cmpi        #2,D0
  384.         bne.s        @3
  385.         pea        @string3
  386.         bra.s        @5
  387. @3        cmpi        #1,D0
  388.         bne.s        @delay
  389.         pea        @string4
  390. @5        move    #10,-(SP)
  391.         move    #14,-(SP)
  392.         _MoveTo
  393.         _DrawString
  394. @delay        
  395.         lea        @downTime,A0
  396.         move.l    Ticks,(A0)
  397.  
  398. /* wait until the mouse is released or 60 ticks, which ever is longer */
  399. @waitTilMouseUp
  400.         subq.l    #2,SP
  401.         _StillDown
  402.         tst        (SP)+
  403.         bne.s        @waitTilMouseUp
  404.         lea        @downTime,A0
  405.         move.l    (A0),D0
  406.         addi.l    #MESSAGE_DELAY,D0
  407.         cmp.l    Ticks,D0
  408.         bgt.s        @waitTilMouseUp
  409.  
  410. /* erase the string */
  411.         pea        @stringRect
  412.         _EraseRect
  413.  
  414. /* dispose of the clipRgn we created */
  415.         move.l    WMgrPort,A1
  416.         move.l    OFFSET(GrafPort,clipRgn)(A1),-(SP)
  417.         _DisposeRgn
  418. /* restore the old clip rgn */
  419.         lea        @saveClip,A0
  420.         move.l    WMgrPort,A1
  421.         move.l    (A0),OFFSET(GrafPort,clipRgn)(A1)
  422.         
  423. /* restore the old port */
  424.         move.l    (A5),A0
  425.         lea        @savePort,A1
  426.         move.l    (A1),(A0)
  427.         rts
  428.         
  429.         
  430. @eventRecPtr        dc.l        0
  431. @menus            dc        0    /* TRUE while menus that exist are not being shown */
  432. @downTime        dc.l        0
  433. @clicks            dc        0    /* number of menus clicks until menus return */
  434. @words            dc        0    /* number of 16 bit words in one row of screen */
  435. @saveLoc            dc.l        0    /* address of our patch in the system heap */
  436. @savePort        dc.l        0
  437. @saveClip            dc.l        0
  438. @drawMenuBarAddr    dc.l        0
  439. @hiliteMenuAddr    dc.l        0
  440.  
  441. /* this rect should be big enough to enclose the longest of the strings. */
  442. @stringRect        dc        0,8,MENU_BAR_HEIGHT - 1,300
  443.  
  444. /* define the pascal strings. Does LSC provide a better way than this? */
  445. @string1            dc.b        13,'N','o',' ','M','e','n','u','s',' ','H','e','r','e'
  446. @string2            dc.b        14,'S','t','i','l','l',' ','N','o',' ','M','e','n','u','s'
  447. @string3            dc.b        7,'N','o','t',' ','Y','e','t'
  448. @string4            dc.b        25,'W','h','e','r','e',' ','D','i','d',' ','T','h','o','s','e'
  449.                 dc.b        ' ','M','e','n','u','s',' ','G','o','?'
  450. @last
  451.     }
  452. }
  453.